<!-- OpenCV.js快速入门指南(博文) https://blog.csdn.net/jm_12138/article/details/122910737 -->
<!-- OpenCV.js官方文档 https://docs.opencv.org/4.5.5/d0/d84/tutorial_js_usage.html -->
<!-- OpenCV.js官方文档 模板匹配 https://docs.opencv.org/4.5.5/d8/dd1/tutorial_js_template_matching.html -->
<!-- opencv中的模板匹配--自适应目标匹配 https://blog.csdn.net/qq_46418503/article/details/119675943 -->

<!-- 
    遇到问题解决(!!!可能不是正确的解决方法)
    1.opencv.js加载慢，下载到本地使用
    2.图片加载跨域问题，使用live server插件运行html文件
    3.cv.Mat is not a constructor  
        function onOpenCvReady() {
            cv['onRuntimeInitialized']=()=>{
                // do all your work here
                。。。
            }
        }
    4. Promise.all加载完图片后，再使用cv
    5. new cv.Size(col, row)
 !!!6. new cv.Rect(matchLoc, point) 得到的rect width，height undefined?? 直接自己写一个Rect_<_Tp>::Rect_
 !!!7.opencv.js 不支持 Mat Mat::operator()( const Rect& roi ) const用 Mat.roi(rect)代替 如：let ROI_img = img.roi(img_ROI);
    8. cv.resize 1,2参数类型检查 不能是undefined,使用变量时初始化 如： let matDst1 = new cv.Mat();
    9. cv.CV_BGR2GRAY变量废弃用 cv.COLOR_BGR2GRAY
 !!!10. opencv.js不支持 dct 使用 cv.dft https://github.com/opencv/opencv/issues/22383
 !!!11. 强制类型转换matDst2.ptr < uchar > (i),opencv.js找到个类似的  Mat.ptr(i) 如：let data1 = matDst1.ucharPtr(i);
 !!!12. 模拟数据结果就一个20符合 ，用<= ; pHash(ROI_img, templ) <= 20
    13. 模板canvas尺寸影响是否能查到结果
    14. 有多个结果时，取iDiffNum最小的那个
    
 -->

<!-- 
    待优化
        循环缩放模板图片的次数是20，博文是10；如何从次数角度优化， iDiffNum为0直接跳出循环
        程序执行时间太长了。

  -->

<!-- 
    思考
        案例 模板图片与待搜索图片一致，或比它大，那有没有比它小的时候？？可以缩小，那也可以放大，但不能比待搜索条件大
 -->

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>opencv自适应目标匹配+数字识别（二值化图片）</title>
    <!-- <script src='https://docs.opencv.org/4.8.0/opencv.js' onload="openCvReady()" type="text/javascript"></script> -->
    <!-- OpenCV.js 4.5.5 版本 -->
    <!-- <script src='https://docs.opencv.org/4.5.5/opencv.js'></script> -->

    <!-- <script src='./opencv_4.5.5.js' type="module"></script> -->

    <!-- You may want to load opencv.js asynchronously by async attribute in <script> tag. To be notified when opencv.js is ready, you can register a callback to onload attribute. -->
    <script async src="./opencv_4.5.5.js" onload="openCvReady()" type="text/javascript"></script>
    <!-- <script async src="./opencv_4.8.0.js" onload="openCvReady()" type="text/javascript"></script> -->

    <!-- ./icons.png -->
    <!-- <canvas id="imageCanvasInput" width="400" height="400"></canvas>
    <canvas id="canvasOutput" width="400" height="400"></canvas> -->

    <!-- screenshot1 ，runes1-->
    <canvas id="imageCanvasInput" width="1080" height="2400"></canvas>
    <canvas id="canvasOutput" width="1080" height="2400"></canvas>

    <!-- screenshot2 -->
    <!-- <canvas id="imageCanvasInput" width="1240" height="2772"></canvas>
    <canvas id="canvasOutput" width="1240" height="2772"></canvas> -->

    <!-- bag_rune -->
    <!-- <canvas id="imageCanvasInput" width="120" height="120"></canvas>
    <canvas id="canvasOutput" width="120" height="120"></canvas> -->
    <hr />
    <!-- ./icon.png -->
    <!-- <canvas id="templateCanvasInput" width="70" height="70"></canvas> -->
    <!-- ./icon_l.png -->
    <!-- <canvas id="templateCanvasInput" width="140" height="140"></canvas> -->
    <!-- ./icon_gold.png -->
    <!-- <canvas id="templateCanvasInput" width="100" height="60"></canvas> -->
    <!-- ./icon_gold_l.png -->
    <!-- <canvas id="templateCanvasInput" width="150" height="90"></canvas> -->
    <!-- ./runes/rune_1_SIL.jpg -->
    <!-- <canvas id="templateCanvasInput" width="80" height="80"></canvas> -->
    <!-- ./nums/num_1.jpg -->
    <canvas id="templateCanvasInput" width="40" height="40"></canvas>

</head>

<body>
    <!-- <script type="module">
        import * as cv from './opencv_4.5.5.js';
        console.log(cv);
    </script> -->
    <script>
        async function openCvReady() {
            // 脚本加载完成
            // console.log(cv);
            // opencv.js 在真正初始化之前加载并触发 onload 事件。为了等到 opencv.js 真正准备好，opencv.js 提供了一个挂机“onRuntimeInitialized”。像这样使用它：

            // var img1 = new Image();
            // img.crossOrigin = 'anonymous';
            // img1.src = './img1.png';
            console.time("执行时间");
            console.log(1);

            // var src1 = "./img1.png";
            // var src2 = "./img2.png";
            // var src1 = "./icons.png";
            // var src1 = "./screenshot1.jpg";
            // var src1 = "./screenshot2.jpg";
            var src1 = "./runes1.jpg";
            // var src1 = "./bag_rune.jpg";


            // var src2 = "./icon.png";
            // var src2 = "./icon_l.png";
            // var src2 = "./icon_gold.png";
            // var src2 = "./icon_gold_l.png";
            // var src2 = "./runes/rune_SIL.png";
            // var src2 = "./runes/rune_1_SIL.jpg";

            var src2 = "./nums/num_1.png";
            // var src2 = "./nums/num_2.png";
            // var src2 = "./nums/num_3.png";
            // var src2 = "./nums/num_4.png";
            // var src2 = "./nums/num_5.png";
            // var src2 = "./nums/num_6.png"; // 素材不一致
            // var src2 = "./nums/num_7.png";
            // var src2 = "./nums/num_8.png";
            // var src2 = "./nums/num_9.png"; // 素材不一致
            // var src2 = "./nums/num_0.png"; // 素材不一致


            await Promise.all([await loadImg(src1).then(img1 => {
                // console.log(img1);
                var canvas1 = document.getElementById('imageCanvasInput');
                var ctx1 = canvas1.getContext('2d', {
                    willReadFrequently: true,
                });
                // img1.onload = function () {
                //     ctx1.drawImage(img1, 0, 0);
                // }
                ctx1.drawImage(img1, 0, 0);


                var canvas3 = document.getElementById('canvasOutput');
                var ctx3 = canvas3.getContext('2d', {
                    willReadFrequently: true,
                });
                ctx3.drawImage(img1, 0, 0);

                console.log(12);
            }), await loadImg(src2).then(img2 => {
                // console.log(img2);
                var canvas2 = document.getElementById('templateCanvasInput');
                var ctx2 = canvas2.getContext('2d', {
                    willReadFrequently: true,
                });
                ctx2.drawImage(img2, 0, 0);
                console.log(13);
            })]);

            console.log(2);
            cv['onRuntimeInitialized'] = () => {
                console.log(3);
                // do all your work here


                // let src = cv.imread('imageCanvasInput'); // 待查询
                let src_original = cv.imread('imageCanvasInput'); // 待查询
                let low_hsv_src = new cv.Mat(src_original.rows, src_original.cols, src_original.type(), [240, 0,
                    0, 255
                ]);
                let high_hsv_src = new cv.Mat(src_original.rows, src_original.cols, src_original.type(), [255,
                    255, 255, 255
                ]);
                let src = new cv.Mat();
                cv.inRange(src_original, low_hsv_src, high_hsv_src, src);

                cv.imshow("canvasOutput", src);
                src = cv.imread("canvasOutput"); // 二值化图

                // let templ = cv.imread('templateCanvasInput'); // 模板
                let templ_original = cv.imread('templateCanvasInput'); // 模板
                let low_hsv_templ = new cv.Mat(templ_original.rows, templ_original.cols, templ_original.type(),
                    [240, 0, 0, 255]);
                let high_hsv_templ = new cv.Mat(templ_original.rows, templ_original.cols, templ_original.type(),
                    [255, 255, 255, 255]);
                let templ = new cv.Mat();
                cv.inRange(templ_original, low_hsv_templ, high_hsv_templ, templ);

                cv.imshow("templateCanvasInput", templ);
                templ = cv.imread("templateCanvasInput"); // 二值化图

                let dst = new cv.Mat();
                let mask = new cv.Mat();

                // We use the function: cv.matchTemplate (image, templ, result, method, mask = new cv.Mat())

                // Parameters
                // image	image where the search is running. It must be 8-bit or 32-bit floating-point.
                // templ	searched template. It must be not greater than the source image and have the same data type.
                // result	map of comparison results. It must be single-channel 32-bit floating-point.
                // method	parameter specifying the comparison method(see cv.TemplateMatchModes).
                // mask	mask of searched template. It must have the same datatype and size with templ. It is not set by default.
                // console.log(cv.Size);


                // -----循环缩放模板图片
                let res = null; // 保存最好的结果
                const default_d_res = 100;
                let iDiffNum_res = default_d_res; // iDiffNum值越小，对应越好的结果
                let size_res = null;
                let const_i; // 记录结果，方便调试

                // for (let i = 0; i < 20; i++) {
                // for (let i = -10; i < 20; i++) {// 放大 找 符石
                // for (let i = -10; i < 0; i++) {// 放大 找 符石
                for (let i = 0; i < 10; i++) { // rune _ rect上 缩小找 数字
                    let templ_c = templ.clone();
                    let col = templ.cols - i * 0.05 * templ.cols;
                    let row = templ.rows - i * 0.05 * templ.rows;
                    // console.log(col,row);
                    // console.log(cv.resize);
                    // console.log(cv.size);
                    // console.log(new cv.Size(col, row));
                    let size = new cv.Size(col, row)
                    cv.resize(templ_c, templ_c, size);


                    // -----模板匹配
                    // cv.matchTemplate(src, templ_c, dst, cv.TM_CCOEFF_NORMED, mask);
                    cv.matchTemplate(src, templ_c, dst, cv.TM_SQDIFF);
                    // cv.matchTemplate(src, templ_c, dst, 0); // 0: cv.TM_SQDIFF 4:cv.TM_CCOEFF 5:cv.TM_CCOEFF_NORMED

                    let result = cv.minMaxLoc(dst, mask);
                    // console.log(result);
                    let maxLoc = result.maxLoc;
                    let minLoc = result.minLoc;
                    let minVal = result.minVal || -1;
                    let maxVal = result.maxVal;
                    let matchLoc;

                    let ROI = new cv.Rect(minLoc.x, maxLoc.y, col, row);

                    // let src_c = src.clone();
                    let src_c = src_original.clone(); // 原图上标记
                    matchLoc = minLoc;

                    //-----根据result中最大值位置 画出矩形和中心点
                    let center = new cv.Point(minLoc.x + templ_c.cols / 2, minLoc.y + templ_c.rows / 2);

                    let color = new cv.Scalar(255, 0, 0, 255);
                    // let point = new cv.Point(maxPoint.x + templ.cols, maxPoint.y + templ.rows);
                    let point = new cv.Point(matchLoc.x + templ_c.cols, matchLoc.y + templ_c.rows);
                    // cv.rectangle(src, maxPoint, point, color, 2, cv.LINE_8, 0);
                    // console.log(src_c, matchLoc);

                    cv.rectangle(src_c, matchLoc, point, color, 2, cv.LINE_8, 0); // 二值化图上标记

                    // cv.imshow('canvasOutput', src); // 结果




                    // -----获取匹配得到的区域
                    // console.log(matchLoc, point);
                    // console.log(cv.Rect);
                    /***
                     * Rect_<_Tp>::Rect_(const Point_<_Tp>& pt1, const Point_<_Tp>& pt2)
                        {
                            x = std::min(pt1.x, pt2.x);
                            y = std::min(pt1.y, pt2.y);
                            width = std::max(pt1.x, pt2.x) - x;
                            height = std::max(pt1.y, pt2.y) - y;
                        }
                    */
                    function _Rect(pt1, pt2) {
                        let x = pt1.x < pt2.x ? pt1.x : pt2.x;
                        let y = pt1.y < pt2.y ? pt1.y : pt2.y;
                        let w = pt1.x > pt2.x ? pt1.x : pt2.x - x;
                        let h = pt1.y > pt2.y ? pt1.y : pt2.y - y;
                        return new cv.Rect(x, y, w, h);
                    }
                    // let img_ROI = new cv.Rect(matchLoc, new cv.Point(matchLoc.x + templ_c.cols, matchLoc.y + templ_c.rows));// width,height undefined???
                    // let img_ROI = new cv.Rect(matchLoc.x, matchLoc.y, templ_c.cols, templ_c.rows); // 直接这样写也得结果 但不知道会不会有问题
                    let img_ROI = _Rect(matchLoc, point); // 模仿Rect_<_Tp>::Rect_
                    let img = src.clone();
                    // console.log(img, img_ROI);
                    // let ROI_img = img(img_ROI); // img is not a function
                    /***
                     * Mat Mat::operator()( const Rect& roi ) const
                     *   {
                     *       return Mat(*this, roi);
                     *   }
                     *
                     */
                    // let ROI_img = new cv.Mat(img, img_ROI); // return Mat(*this, roi); js不支持
                    let ROI_img = img.roi(img_ROI); // return Mat(*this, roi);
                    // console.log(ROI_img);
                    // cv.imshow('canvasOutput', ROI_img);




                    //-----进行相似度比较
                    let iDiffNum = pHash(ROI_img, templ);
                    // console.log("iDiffNum:", iDiffNum);
                    console.log(`i: ${i}`, size, `iDiffNum: ${iDiffNum}`);
                    // if (pHash(ROI_img, templ) < 20) {
                    // if (iDiffNum <= 20) { // ！！！！缩放识别 这一步<=跟原博文不一致
                    // if (iDiffNum <= 21) { // ！！！！rune识别这一步<=跟原博文不一致
                    if (iDiffNum <= 30) { // ！！！！数字识别
                        if (iDiffNum == 0) {
                            iDiffNum_res = 0;
                            size_res = size;
                            res = src_c;
                            const_i = i;
                            // 完全一致，直接跳出循环
                            break;
                        } else {
                            if (iDiffNum_res > iDiffNum) {
                                iDiffNum_res = iDiffNum;
                                size_res = size;
                                res = src_c;
                                const_i = i;
                            }
                        }
                    }

                    // console.log("^^^");
                }


                if (iDiffNum_res == default_d_res) {
                    console.log("没匹配到……");
                } else {
                    console.log("最终结果:", `i: ${const_i}`, `iDiffNum: ${iDiffNum_res}`, size_res);
                    cv.imshow("canvasOutput", res);
                }
                res !== null && res.delete();

                src.delete();
                dst.delete();
                mask.delete();

                console.timeEnd("执行时间");

            };




        }


        function pHash(matSrc1, matSrc2)
        //int main()
        {
            let matDst1 = new cv.Mat();
            let matDst2 = new cv.Mat(); // cv.resize 1,2参数类型检查 不能是undefined

            //    let matSrc1 = cv.imread("../1.jpg");
            //    let matSrc2 = cv.imread("../3.jpg");
            // console.log(new cv.Size(32, 32), cv.INTER_CUBIC);
            cv.resize(matSrc1, matDst1, new cv.Size(32, 32), 0, 0, cv.INTER_CUBIC);
            cv.resize(matSrc2, matDst2, new cv.Size(32, 32), 0, 0, cv.INTER_CUBIC);

            // console.log(cv.CV_BGR2GRAY,cv.COLOR_BGR2GRAY);// cv.CV_BGR2GRAY 废弃
            cv.cvtColor(matDst1, matDst1, cv.COLOR_BGR2GRAY);
            cv.cvtColor(matDst2, matDst2, cv.COLOR_BGR2GRAY);

            matDst1.convertTo(matDst1, cv.CV_32F);
            matDst2.convertTo(matDst2, cv.CV_32F);

            // opencv.js不支持 dct  
            // cv.dct(matDst1, matDst1);
            // cv.dct(matDst2, matDst2);

            // ！！！！这边跟原博文不一致，换成了dft
            // Is opencv.js supports dft and dct now? #22383 https://github.com/opencv/opencv/issues/22383
            cv.dft(matDst1, matDst1);
            cv.dft(matDst2, matDst2);

            let iAvg1 = 0;
            let iAvg2 = 0;
            let arr1 = new Array(64);
            let arr2 = new Array(64);

            for (let i = 0; i < 8; i++) {
                // uchar * data1 = matDst1.ptr < uchar > (i);
                // uchar * data2 = matDst2.ptr < uchar > (i);

                // let data1 = matDst1.ptr(i);
                // let data2 = matDst2.ptr(i);

                let data1 = matDst1.ucharPtr(i);
                let data2 = matDst2.ucharPtr(i);

                // console.log(data1, data2);

                let tmp = i * 8;

                for (let j = 0; j < 8; j++) {
                    let tmp1 = tmp + j;

                    arr1[tmp1] = data1[j];
                    arr2[tmp1] = data2[j];

                    iAvg1 += arr1[tmp1];
                    iAvg2 += arr2[tmp1];
                }
            }

            iAvg1 /= 64;
            iAvg2 /= 64;

            for (let i = 0; i < 64; i++) {
                arr1[i] = (arr1[i] >= iAvg1) ? 1 : 0;
                arr2[i] = (arr2[i] >= iAvg2) ? 1 : 0;
            }

            let iDiffNum = 0;

            for (let i = 0; i < 64; i++) {
                if (arr1[i] != arr2[i]) {
                    ++iDiffNum;
                }
            }

            //    cout<<iDiffNum<<endl;
            return iDiffNum;
        }


        function loadImg(url) {
            return new Promise((resolve, reject) => {
                let img = new Image();
                // img.crossOrigin = 'anonymous';
                // 跨域 直接用live server插件运行
                img.src = url;
                img.onload = () => {
                    resolve(img);
                };
                img.onerror = reject;
            });
        }
    </script>
</body>

</html>